﻿using Newtonsoft.Json.Linq;
using Niknak.Social.Common.Http;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using AsyncBridge;

namespace Niknak.Social.Twitter
{
    /// <summary>
    /// Provides basic methods to retrieve a Twitter timeline for a single user, either using default
    /// class implementations or types provided by the calling code.
    /// </summary>
    /// <typeparam name="Settings">Type that implements OAuthSettings to provide OAuth security details</typeparam>
    public static class UserTimeline<Settings> 
        where Settings : ISettings, new()
    {
        private const int DEFAULTCOUNT = 10;
        private const bool DEFAULTINCLUDERTS = false;
        private const string DEFAULTSINCEMESSAGEID = "1";
        private const string APITIMELINEURL = "https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name={0}&count={1}&include_rts={2}&since_id={3}";
        private const string APISTATUSESUPDATEURL = "https://api.twitter.com/1.1/statuses/update.json";

        //#region Basic

        //public static List<Message> GetTweets(string screenName)
        //{
        //    return GetTweets<Message, User, Media, MediaSize>(screenName);
        //}

        //public static List<Message> GetTweets(string screenName, int count)
        //{
        //    return GetTweets<Message, User, Media, MediaSize>(screenName, count);
        //}

        //public static List<Message> GetTweets(string screenName, int count, bool includeRTs)
        //{
        //    return GetTweets<Message, User, Media, MediaSize>(screenName, count, includeRTs);
        //}

        //public static List<Message> GetTweets(string screenName, int count, bool includeRTs, string sinceMessageId)
        //{
        //    return GetTweets<Message, User, Media, MediaSize>(screenName, count, includeRTs, sinceMessageId);
        //}

        //#endregion

        //#region Bit of both

        //public static List<Tweet> GetTweets<Tweet>(string screenName)
        //    where Tweet : IMessage, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(screenName);
        //}

        //public static List<Tweet> GetTweets<Tweet>(string screenName, int count)
        //   where Tweet : IMessage, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(screenName, count);
        //}

        //public static List<Tweet> GetTweets<Tweet>(string screenName, int count, bool includeRTs)
        //   where Tweet : IMessage, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(screenName, count, includeRTs);
        //}

        //public static List<Tweet> GetTweets<Tweet>(string screenName, int count, bool includeRTs, string sinceMessageId)
        //   where Tweet : IMessage, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(screenName, count, includeRTs, sinceMessageId);
        //}

        //public static List<Tweet> GetTweets<Tweet, User>(string screenName)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(screenName);
        //}

        //public static List<Tweet> GetTweets<Tweet, User>(string screenName, int count)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(screenName, count);
        //}

        //public static List<Tweet> GetTweets<Tweet, User>(string screenName, int count, bool includeRTs)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(screenName, count, includeRTs);
        //}

        //public static List<Tweet> GetTweets<Tweet, User>(string screenName, int count, bool includeRTs, string sinceMessageId)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(screenName, count, includeRTs, sinceMessageId);
        //}

        //public static List<Tweet> GetTweets<Tweet, User, Media>(string screenName)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //    where Media : IMedia, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(screenName);
        //}

        //public static List<Tweet> GetTweets<Tweet, User, Media>(string screenName, int count)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //    where Media : IMedia, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(screenName, count);
        //}

        //public static List<Tweet> GetTweets<Tweet, User, Media>(string screenName, int count, bool includeRTs)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //    where Media : IMedia, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(screenName, count, includeRTs);
        //}

        //public static List<Tweet> GetTweets<Tweet, User, Media>(string screenName, int count, bool includeRTs, string sinceMessageId)
        //    where Tweet : IMessage, new()
        //    where User : IUser, new()
        //    where Media : IMedia, new()
        //{
        //    return GetTweets<Tweet, User, Media, MediaSize>(screenName, count, includeRTs, sinceMessageId);
        //}

        //#endregion

        #region Generic

        public static List<IMessage> GetTweets(string screenName)
        {
            if (string.IsNullOrEmpty(screenName))
                throw new ArgumentNullException("screenName");

            return GetTweets(screenName, DEFAULTCOUNT, DEFAULTINCLUDERTS, DEFAULTSINCEMESSAGEID);
        }

        public static List<IMessage> GetTweets(string screenName, int count)
        {
            if (string.IsNullOrEmpty(screenName))
                throw new ArgumentNullException("screenName");
            if (count <= 0)
                throw new ArgumentNullException("count");

            return GetTweets(screenName, count, DEFAULTINCLUDERTS, DEFAULTSINCEMESSAGEID);
        }

        public static List<IMessage> GetTweets(string screenName, int count, bool includeRTs)
        {
            if (string.IsNullOrEmpty(screenName))
                throw new ArgumentNullException("screenName");
            if (count <= 0)
                throw new ArgumentNullException("count");

            return GetTweets(screenName, count, includeRTs, DEFAULTSINCEMESSAGEID);
        }

        public static List<IMessage> GetTweets(string screenName, int count, bool includeRTs, string sinceMessageId)
        {
            if (string.IsNullOrEmpty(screenName))
                throw new ArgumentNullException("screenName");
            if (count <= 0)
                throw new ArgumentNullException("count");
            if (string.IsNullOrEmpty(sinceMessageId) || sinceMessageId == "0")
                throw new ArgumentNullException("sinceMessageId");

            List<IMessage> outList = new List<IMessage>();
            List<JObject> tweets = new List<JObject>();

            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            using (HttpClient client = new HttpClient(new OAuthMessageHandler<Settings>(new HttpClientHandler())))
            {
                if (client == null)
                    throw new Exception("Unable to initiate HttpClient object");

                string requestUrl = string.Format(
                    APITIMELINEURL,
                    screenName,
                    count,
                    includeRTs.ToString().ToLower(),
                    sinceMessageId
                );

                HttpResponseMessage response = null;

                try
                {
                    using (var A = AsyncHelper.Wait)
                    {
                        A.Run(client.GetAsync(requestUrl), res => response = res);
                    }
                }
                catch (Exception ex)
                {
                    //only throw an exception if it's not the 12hr ssl/tls error
                    if (!(ex.InnerException != null && ex.InnerException.InnerException != null && ex.InnerException.InnerException.Message.ToLowerInvariant().Contains("could not create ssl/tls secure channel")))
                        throw;
                }
                    
                if (response == null)
                    throw new Exception("Null HTTP response");

                //check that response was successful or throw exception
                response.EnsureSuccessStatusCode();

                //necessary when retrieving more than about 15 tweets at a time
                //MediaTypeFormatter.SkipStreamLimitChecks = true;

                //read response as JArray and dump into list for further processing
                JArray messages = null;

                try
                {
                    using (var A = AsyncHelper.Wait)
                    {
                        A.Run(response.Content.ReadAsAsync<JArray>(), res => messages = res);
                    }
                }
                catch (Exception ex)
                {
                    //only throw an exception if it's not the 12hr ssl/tls error
                    if (!(ex.InnerException != null && ex.InnerException.InnerException != null && ex.InnerException.InnerException.Message.ToLowerInvariant().Contains("could not create ssl/tls secure channel")))
                        throw;
                }

                if (messages != null)
                    foreach (JObject message in messages)
                        tweets.Add(message);
            }

            if (tweets != null)
                foreach (JObject tweet in tweets)
                {
                    //we need to do some checks on the message retrieved to ensure it's not fubar
                    if (!Utils.IsValidTweetJson(tweet))
                        continue;

                    outList.Add(Utils.ParseTweetJson<Settings>(tweet));
                }

            return outList;
        }

        #endregion

        /// <summary>
        /// Update the status with the supplied message. No more than 140 chars, URL wrapping may affect this
        /// </summary>
        /// <returns>True if request was successful, exception if not</returns>
        public static bool PostUpdate(string status)
        {
            if (string.IsNullOrEmpty(status))
                throw new ArgumentNullException("status");
            if (status.Length > 140)
                throw new ArgumentOutOfRangeException("status");

            return PostUpdate(status, string.Empty);
        }

        /// <summary>
        /// Update the status with the supplied message, created in reply to an existing message ID.
        /// No more than 140 chars, URL wrapping may affect this. Status MUST start with @screenname of
        /// the author of the message being replied to.
        /// </summary>
        /// <returns>True if request was successful, exception if not</returns>
        public static bool PostUpdate(string status, string replyToMessageId)
        {
            if (string.IsNullOrEmpty(status))
                throw new ArgumentNullException("status");
            if (status.Length > 140)
                throw new ArgumentOutOfRangeException("status");

            bool outVal = false;

            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            using (HttpClient client = new HttpClient(new OAuthMessageHandler<Settings>(new HttpClientHandler())))
            {
                string postBody = "status=" + Utils.SpecialEncode(status);

                if (!string.IsNullOrEmpty(replyToMessageId))
                    postBody += "&in_reply_to_status_id=" + replyToMessageId;

                Task postMessage = client.PostAsync(APISTATUSESUPDATEURL + "?" + postBody, new StringContent(""))
                    .ContinueWith(postTask =>
                {
                    HttpResponseMessage response = postTask.Result;

                    if (response.IsSuccessStatusCode)
                        outVal = true;

                    if (!outVal)
                        throw new Exception("Error: " + response.Content.ReadAsStringAsync().Result);
                });

                Task.WaitAll(postMessage);
            }

            return outVal;
        }
    }
}
